/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package course.ya.doc.from.code.model.drawing;

import course.ya.doc.from.code.model.ast.StatementTree;
import course.ya.doc.from.code.model.ast.StatementTreeNode;
import course.ya.doc.from.code.model.statements.*;
import java.io.File;
import java.lang.Exception;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

/**
 *
 * @author Наташа
 */
public class Draftsman {

    class Correspondance {

        String nodeName;
        String nodeText;

        Correspondance(String name, String text) {
            this.nodeName = name;
            this.nodeText = text;
        }
    }

    protected StatementTree tree;
    private Stack<StatementTreeNode> stack;
    private Stack<String> doStack = new Stack<String>();
    ;
    private Stack<String> condStack = new Stack<String>();
    private Stack<String> ifCondStack = new Stack<String>();
    private List<StatementTreeNode> visited;
    private List<StatementTreeNode> loopEnds = new ArrayList<StatementTreeNode>();
    private List<String> conditions;
    private String filename;
    private String[] labels;
    private List<Correspondance> correspondances;
    GraphViz gv;
    int index = 0;

    public Draftsman(StatementTree tree, String filename) {
        this.tree = tree;
        this.stack = new Stack<StatementTreeNode>();
        this.visited = new ArrayList<StatementTreeNode>();
        this.conditions = new ArrayList<String>();
        this.correspondances = new ArrayList<Correspondance>();
        this.filename = filename;
    }

    //Check if a node has unvisited children
    private int checkChildren(StatementTreeNode x) {
        try {

            List<StatementTreeNode> c = x.getChildren();
            boolean t = visited.contains(c.get(0));
            int i = 0;
            while ((t) && (i < c.size())) {
                t = visited.contains(c.get(i));
                i++;
            }
            return i;
        } catch (Exception ex) {
            return 100000000;
        }
    }

    //Check if a node has unvisited brothers
    private boolean checkBrothers(StatementTreeNode x) {
        boolean t;
        try {
            List<StatementTreeNode> c = x.getParent().getChildren();
            //t = !visited.containsAll(c);
            t = c.get(c.indexOf(x) + 1) != null;
        } catch (Exception ex) {
            t = false;
        }
        return t;
    }

    //returns right brother of the node or of its first found (grand-)parent which has one 
    private StatementTreeNode falseBranch(StatementTreeNode x) {
        try {
            while ((x.getParent().getChildren()).get(x.getParent().getChildren().indexOf(x) + 1) == null) {
                x = x.getParent();
            }
        } catch (ArrayIndexOutOfBoundsException ex) {
            x = x.getParent();
        }
        return (x.getParent().getChildren()).get(x.getParent().getChildren().indexOf(x) + 1);
    }

    //returns the last statement of the loop
    private StatementTreeNode endLoopBranch(StatementTreeNode x) {
        StatementTreeNode temp = x;
        boolean end = x.getChildren().isEmpty();
        while (!end) {
            temp = temp.getChildren().get(temp.getChildren().size() - 1);
            end = temp.getChildren().isEmpty();
        }
        return temp;
    }

    private void makeDrawingRules(StatementTreeNode src, StatementTreeNode dest, boolean cond) {
        AStatement st = src.getStatement();

        if ((st instanceof IfStatement) || (st instanceof ElsIfStatement)) {
            String node1;
            StringBuilder sb = new StringBuilder();
            sb.append(st.toString().replace("\"", "\'"));

            if (cond) {
                node1 = "Node" + index;

                int count = 0;
                for (int i = 0; i < st.toString().length(); i++) {
                    count++;
                    if (count > 15 && sb.charAt(i) == ' ') {
                        sb.insert(i, "\n");
                        count = 0;
                    }
                }

                correspondances.add(new Correspondance(node1, sb.toString().replace('\"', '\'')));
                index++;
                conditions.add(node1);
                ifCondStack.push(node1);
                String node2 = "Node" + index;
                correspondances.add(new Correspondance(node2, dest.getStatement().toString().replace('\"', '\'')));
                gv.addln(node1 + " ->" + node2 + "[label = \"true\"];");
            } else {
                node1 = ifCondStack.pop();
                index++;
                String node2 = "Node" + index;
                gv.addln(node1 + " ->" + node2 + "[label = \"false\"];");
                correspondances.add(new Correspondance(node2, dest.getStatement().toString().replace('\"', '\'')));
            }
            conditions.add(node1);
        } else if ((st instanceof CallMethodStatement) || (st instanceof ExpressionStatement)
                || (st instanceof ReturnStatement) || (st instanceof MethodDeclarationStatement)
                || (st instanceof FieldDeclarationStatement) || (st instanceof ThrowStatement) || (st instanceof BreakStatement)) {
            if (loopEnds.contains(src)) {
                String node1 = "Node" + index;
                index++;
                String node2 = condStack.peek();
                gv.addln(node1 + " ->" + node2 + ";");
                correspondances.add(new Correspondance(node1, st.toString().replace('\"', '\'')));
                return;
            } else if (dest.getStatement() instanceof DoWhileStatement) {

                String node3 = "Node" + index;
                correspondances.add(new Correspondance(node3, dest.getStatement().toString().replace('\"', '\'')));
                doStack.push(node3);

            } else {
                String node1 = "Node" + index;
                correspondances.add(new Correspondance(node1, st.toString().replace('\"', '\'')));
                index++;
                String node2 = "Node" + index;
                correspondances.add(new Correspondance(node2, dest.getStatement().toString().replace('\"', '\'')));
                gv.addln(node1 + " ->" + node2 + ";");
                if (loopEnds.contains(dest)) {
                    String node3 = condStack.peek();
                    gv.addln(node2 + " ->" + node3 + ";");
                }
                if (st instanceof ReturnStatement) {
                    this.ending(index);
                }
            }

        } else if (st instanceof ForStatement) {
            String node1;
            if (cond) {
                node1 = "Node" + index;
                correspondances.add(new Correspondance(node1, st.toString().replace('\"', '\'')));
                index++;
                condStack.push(node1);
                String node2 = "Node" + index;
                correspondances.add(new Correspondance(node2, dest.getStatement().toString().replace('\"', '\'')));
                gv.addln(node1 + " ->" + node2 + "[label = \"true\"];");
            } else {
                node1 = condStack.pop();
                index++;
                String node2 = "Node" + index;
                gv.addln(node1 + " ->" + node2 + "[label = \"false\"];");
                correspondances.add(new Correspondance(node2, dest.getStatement().toString().replace('\"', '\'')));
            }
            conditions.add(node1);
            StatementTreeNode n = endLoopBranch(src);
            loopEnds.add(n);
        } else if (st instanceof WhileStatement) {
            String node1, node2;
            if (!doStack.empty()) {
                node1 = "Node" + index;
                condStack.push(node1);
                index++;
                conditions.add(node1);
                correspondances.add(new Correspondance(node1, st.toString().replace('\"', '\'')));
                node2 = doStack.peek();
                String node3 = "Node" + index;
                correspondances.add(new Correspondance(node3, dest.getStatement().toString().replace('\"', '\'')));
                gv.addln(node2 + " ->" + node3 + ";");
            } else {
                if (cond) {
                    node1 = "Node" + index;
                    correspondances.add(new Correspondance(node1, st.toString().replace('\"', '\'')));
                    index++;
                    conditions.add(node1);
                    condStack.push(node1);
                    node2 = "Node" + index;
                    correspondances.add(new Correspondance(node2, dest.getStatement().toString().replace('\"', '\'')));
                    gv.addln(node1 + " ->" + node2 + "[label = \"true\"];");
                    StatementTreeNode n = endLoopBranch(src);
                    loopEnds.add(n);
                } else {
                    if (!condStack.empty()) {
                        node1 = condStack.pop();
                        //index++;
                        conditions.add(node1);
                        node2 = "Node" + index;
                        index++;
                        gv.addln(node1 + " ->" + node2 + "[label = \"false\"];");
                        correspondances.add(new Correspondance(node2, dest.getStatement().toString().replace('\"', '\'')));
                    }
                }

            }
        } else if (st instanceof DoWhileStatement) {
            //String node1 = "Node"+index;
            //index++;
            //doStack.add(node1);
        }
    }

    void jmpUp(StatementTreeNode src) {
        String node1 = "Node" + index;
        if (doStack.empty()) {
            String node2 = condStack.peek();
            gv.addln(node1 + " ->" + node2 + ";");
        } else {
            String node2 = condStack.pop();
            String node3 = doStack.pop();
            gv.addln(node1 + " ->" + node2 + ";");
            gv.addln(node2 + " ->" + node3 + "[label = \"true\"];");
            conditions.add(node2);
        }
        correspondances.add(new Correspondance(node1, src.getStatement().toString().replace('\"', '\'')));
    }

    public void beginning() {
        gv.addln("stNode -> Node0; stNode [label=\" \" shape=\"ellipse\" ];");
    }

    public void ending(int index) {
        String node1 = "Node" + index;
        String EndNode = String.valueOf(index);
        gv.addln(node1 + " -> EndNode; EndNode [style=\"filled\" shape=\"ellipse\" fillcolor=\"black\"];");
    }

    public void drawLabels() {
        for (Correspondance c : correspondances) {
            gv.addln(c.nodeName + "[label = \"" + c.nodeText + "\"]");
        }
    }

    private void traverse() {

        StatementTreeNode curr, next, t, up;
        stack.push(tree.getRoot());
        curr = tree.getRoot();
        visited.add(tree.getRoot());
        while (!stack.empty()) {
            t = stack.peek();
            if (((checkChildren(t)) < t.getChildren().size()) && (t.getChildren().size() != 0)) {
                curr = t;
                next = t.getChildren().get(checkChildren(t));
                visited.add(t);
                makeDrawingRules(curr, next, true);
                stack.push(next);
                curr = next;
                if (loopEnds.contains(t) && (!(t.getStatement() instanceof BreakStatement))) {
                    jmpUp(t);
                    //loopEnds.remove(t);
                }
            } else {
                t = stack.pop();
                if (loopEnds.contains(t) && (!(t.getStatement() instanceof BreakStatement))) {
                    jmpUp(t);
                    //loopEnds.remove(t);
                } else if ((t.getStatement() instanceof ReturnStatement) || (t.getStatement() instanceof BreakStatement)) {
                    ending(index);
                }
                visited.add(t);
                if (checkBrothers(t)) {
                    curr = t;
                    visited.add(t);
                    List<StatementTreeNode> c = t.getParent().getChildren();
                    next = c.get(c.indexOf(t) + 1);
                    visited.add(next);
                    makeDrawingRules(curr, next, false);
                    stack.push(next);
                    curr = next;
                }
            }
        }
    }

    private void addConditions() {
        for (String st : conditions) {
            gv.addln(st + " [shape = \"diamond\"];");
        }
        gv.addln();
    }

    public void draw() {
        this.gv = new GraphViz();
        gv.addln(gv.start_graph());
        gv.addln("rankdir=TB");
        gv.addln("node [shape=\"rectangle\"];");
        beginning();
        addConditions();
        traverse();
        ending(index);
        drawLabels();
        addConditions();
        gv.addln(gv.end_graph());
        String type = "svg";
        File out = new File(filename + "." + type);
        System.out.println(gv.getDotSource());
        gv.writeGraphToFile(gv.getGraph(gv.getDotSource(), type), out);
    }

    public byte[] getByteArray() {
        if (gv != null) {
            return gv.getGraph(gv.getDotSource(), "svg");
        } else {
            this.draw();
            return gv.getGraph(gv.getDotSource(), "svg");
        }
    }
}
